<?php
declare(strict_types=1);

namespace CommonBundle\Service;

use CommonBundle\Entity\Balance;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Exception\ValidatorException;

final class BalanceService extends BaseService
{
    /** @var ContainerInterface */
    protected $container;
    /** @var EntityManager|object */
    protected $em;
    /** @var ObjectRepository|EntityRepository */
    protected $rep;

    function __construct(ContainerInterface $container)
    {
        parent::__construct($container, Balance::class);
    }

    public function exchange(
        UserInterface $fromUser, UserInterface $toUser,
        string $fromCurrency, string $toCurrency, float $amount,
        string $sendMessage = '', string $receiveMessage = ''): bool
    {
        $optionService = $this->container->get(OptionService::class);

        if($fromCurrency === $toCurrency) {
            $exchangedAmount = $amount;
        }
        else {
            $rateConfKey = sprintf('EXCHANGE_%s_TO_%s_RATE', trim(strtoupper($fromCurrency)), trim(strtoupper($toCurrency)));
            if(empty($rateConfKey))
                throw new ValidatorException('Current currency cannot be exchanged.');

            $transferRate = floatval($optionService->get($rateConfKey)->getValue());
            $exchangedAmount = $amount * $transferRate;
        }

        $this->adjust($fromUser, $fromCurrency, -$amount, $sendMessage);
        try {
            $this->adjust($toUser, $toCurrency, $exchangedAmount, $receiveMessage);
            return true;
        } catch (\Exception $exception) {
            $this->adjust($fromUser, $fromCurrency, +$amount, 'Send amount failure, amount reversed.');
            return false;
        }
    }

    public function adjust(UserInterface $user, string $type, float $amount, string $comment = '', array $extraData = [])
    {
        $em = $this->container->get('doctrine.orm.entity_manager');
        $conn = $em->getConnection();

        $balance = $this->get(['user' => $user]);
        $amount = floatval($amount);
        if(empty($amount))
            throw new ValidatorException('Amount cannot be 0.');

        if(empty($user))
            throw new ValidatorException('User cannot be null');

        if (empty($balance)) {
            $conn->insert('balance', [
                'user_id' => $user->getId(),
                'amount' => "0",
                'last_modified_time' => (new \DateTime())->format('Y-m-d H:i:s'),
            ]);

            $balance = $this->get(['user' => $user]);
        }

        $getter = 'get' . ucfirst($type);
        if (\is_callable(array($balance, $getter))) {
            $before = floatval($balance->$getter());
            $preAfter = $before + $amount;

            if ($preAfter >= 0.0) {
                $qb = $this->em->createQueryBuilder();
                $qb->update('CommonBundle:Balance', 'b')
                    ->set("b.$type", "b.$type + :amount")
                    ->where('b.user = :user')
                    ->setParameter('amount', $amount)
                    ->setParameter('user', $user)
                    ->getQuery()
                    ->execute();
            } else {
                throw new ValidatorException(
                    "The $type remains cannot be less then 0.");
            }
        } else {
            throw new ServiceUnavailableHttpException("Unknown error.");
        }

        // write log
        $translator = $this->container->get('translator');
        $conn->insert('balance_log', [
            'user_id' => $user->getId(),
            '`type`' => $type,
            '`before`' => $before,
            'after' => $preAfter,
            'amount' => $amount,
            '`comment`' => $translator->trans($comment),
            'extra_data' => json_encode($extraData),
            'created_time' => (new \DateTime())->format('Y-m-d H:i:s'),
        ]);
    }

    public function increase(UserInterface $user, float $amount, string $comment = '', array $extraData = [])
    {
        return $this->adjust($user, 'amount', $amount, $comment, $extraData);
    }
    public function decrease(UserInterface $user, float $amount, string $comment = '', array $extraData = [])
    {
        return $this->adjust($user, 'amount', -$amount, $comment, $extraData);
    }
}
